#include "stdafx.h"
#include "event/semaphore.hpp"
#include "stdlib/bugcheck.hpp"
#include "stdlib/exception.hpp"
using namespace JHCPP::stdlib;
using namespace JHCPP::stdlib::jhException;

namespace JHCPP
{
	namespace event
	{
	#if defined(JH_OS_WINDOWS)
		CSemaphoreImpl::CSemaphoreImpl(int n, int max)
		{
			jh_assert (n >= 0 && max > 0 && n <= max);

			m_sema = ::CreateSemaphore(NULL, n, max, NULL);
			if (!m_sema)
			{
				throw SystemException("cannot create semaphore");
			}
		}

		CSemaphoreImpl::~CSemaphoreImpl()
		{
			::CloseHandle(m_sema);
		}

		void CSemaphoreImpl::setImpl()
		{
			if (!::ReleaseSemaphore(m_sema, 1, NULL))
			{
				throw SystemException("cannot signal semaphore");
			}
		}

		void CSemaphoreImpl::waitImpl()
		{
			switch (::WaitForSingleObject(m_sema, INFINITE))
			{
			case WAIT_OBJECT_0:
				return;
			default:
				throw SystemException("wait for semaphore failed");
			}
		}

		bool CSemaphoreImpl::waitImpl(long milliseconds)
		{
			switch (WaitForSingleObject(m_sema, milliseconds + 1))
			{
			case WAIT_TIMEOUT:
				return false;
			case WAIT_OBJECT_0:
				return true;
			default:
				throw SystemException("wait for semaphore failed");		
			}
		}
	#elif defined(JH_OS_LINUX)
		CSemaphoreImpl::CSemaphoreImpl(int n, int max)
		: m_n(n),
		m_max(max)
		{
			jh_assert (n >= 0 && max > 0 && n <= max);

			if (pthread_mutex_init(&m_mutex, NULL))
				throw SystemException("cannot create semaphore (mutex)");
			if (pthread_cond_init(&m_cond, NULL))
				throw SystemException("cannot create semaphore (condition)");
		}

		CSemaphoreImpl::~CSemaphoreImpl()
		{
			pthread_cond_destroy(&m_cond);
			pthread_mutex_destroy(&m_mutex);
		}

		void CSemaphoreImpl::setImpl()
		{
			if (pthread_mutex_lock(&m_mutex))	
				throw SystemException("cannot signal semaphore (lock)");
			if (m_n < m_max)
			{
				++m_n;
			}
			else
			{
				pthread_mutex_unlock(&m_mutex);
				throw SystemException("cannot signal semaphore: count would exceed maximum");
			}	
			if (pthread_cond_signal(&m_cond))
			{
				pthread_mutex_unlock(&m_mutex);
				throw SystemException("cannot signal semaphore");
			}
			pthread_mutex_unlock(&m_mutex);
		}

		void CSemaphoreImpl::waitImpl()
		{
			if (pthread_mutex_lock(&m_mutex))
				throw SystemException("wait for semaphore failed (lock)"); 
			while (m_n < 1) 
			{
				if (pthread_cond_wait(&m_cond, &m_mutex))
				{
					pthread_mutex_unlock(&m_mutex);
					throw SystemException("wait for semaphore failed");
				}
			}
			--m_n;
			pthread_mutex_unlock(&m_mutex);
		}

		bool CSemaphoreImpl::waitImpl(long milliseconds)
		{
			int rc = 0;
			struct timespec abstime;

			struct timeval tv;
			gettimeofday(&tv, NULL);
			abstime.tv_sec  = tv.tv_sec + milliseconds / 1000;
			abstime.tv_nsec = tv.tv_usec*1000 + (milliseconds % 1000)*1000000;
			if (abstime.tv_nsec >= 1000000000)
			{
				abstime.tv_nsec -= 1000000000;
				abstime.tv_sec++;
			}

			if (pthread_mutex_lock(&m_mutex) != 0)
				throw SystemException("wait for semaphore failed (lock)"); 
			while (m_n < 1) 
			{
				if ((rc = pthread_cond_timedwait(&m_cond, &m_mutex, &abstime)))
				{
					if (rc == ETIMEDOUT) break;
					pthread_mutex_unlock(&m_mutex);
					throw SystemException("cannot wait for semaphore");
				}
			}
			if (rc == 0) --m_n;
			pthread_mutex_unlock(&m_mutex);
			return rc == 0;
		}

	#endif

		CSemaphore::CSemaphore(int n)
		: CSemaphoreImpl(n, n)
		{
		}

		CSemaphore::CSemaphore(int n, int max)
		: CSemaphoreImpl(n, max)
		{
		}

		CSemaphore::~CSemaphore()
		{
		}

		void CSemaphore::set()
		{
			setImpl();
		}

		void CSemaphore::wait()
		{
			waitImpl();
		}

		void CSemaphore::wait(long milliseconds)
		{
			if (!waitImpl(milliseconds))
				throw TimeoutException();
		}

		bool CSemaphore::tryWait(long milliseconds)
		{
			return waitImpl(milliseconds);
		}
	}//end of namespace event
}//end of namespace JHCPP
